# coding:utf-8
from PyQt5.QtCore import QRectF, QSize, Qt
from PyQt5.QtGui import QPainter, QPixmap, QWheelEvent, QResizeEvent
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsPixmapItem, QGraphicsItem


class Pixmap(QPixmap):
    def __init__(self, fileName: str, *args, **kwargs):
        super().__init__(fileName, *args, **kwargs)
        self._fileName = fileName

    def fileName(self):
        return self._fileName


class ImageGraphicsView(QGraphicsView):
    """
    图片查看器
    """

    def __init__(self, fileName: str = None, parent=None):
        super().__init__(parent)
        self._rotationAngle = 0

        self.zoomInTimes = 0
        self.maxZoomInTimes = 22
        self.pixmap = Pixmap(fileName)
        self.pixmapItem = QGraphicsPixmapItem()
        self.graphicsScene = QGraphicsScene()
        self.displayedImageSize = QSize(0, 0)

        self.__initWidget()

    def __initWidget(self):
        """
        初始化小部件
        :return:
        """
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)  # 隐藏水平滚动条
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)  # 隐藏垂直滚动条
        self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)  # 以鼠标所在位置为锚点进行缩放
        self.pixmapItem.setTransformationMode(Qt.TransformationMode.SmoothTransformation)  # 平滑转型
        self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)  # 平滑像素图变换

        self.pixmapItem.setPixmap(self.pixmap)
        self.graphicsScene.addItem(self.pixmapItem)
        self.setScene(self.graphicsScene)

        self.setStyleSheet('background-color: #ffffff;')

    def setImage(self, fileName: str):
        """
        设置显示的图片
        :param fileName:
        :return:
        """
        self.resetTransform()
        del self.pixmap
        self.pixmap = Pixmap(fileName)
        self.pixmapItem.setPixmap(self.pixmap)
        self.zoomInTimes = 0
        # 调整图片大小
        self.setSceneRect(QRectF(self.pixmap.rect()))
        ratio = self.__getScaleRatio()
        self.displayedImageSize = self.pixmap.size() * ratio
        if ratio < 1:
            self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
        self.pixmapItem.setTransformOriginPoint(self.pixmapItem.boundingRect().center())

    def setOriginalSize(self):
        """
        设置 1:1 大小
        :return:
        """
        self.resetTransform()
        self.setSceneRect(QRectF(self.pixmap.rect()))
        self.__setDragEnabled(self.__isEnableDrag())
        self.zoomInTimes = self.getZoomInTimes(self.pixmap.width())

    def setAdaptation(self):
        """
        缩放以适应
        :return:
        """
        self.setSceneRect(QRectF(self.pixmap.rect()))
        self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
        self.__setDragEnabled(False)
        self.zoomInTimes = 0

    def rotationAngle(self):
        return self._rotationAngle

    def rotateClockwise(self, stepSize: int = 90):
        """
        顺时针旋转
        :param stepSize: 步长，旋转角度
        :return:
        """
        if self.pixmap.fileName() is None:
            return
        self._rotationAngle = self._rotationAngle + stepSize
        self.__rotation(self._rotationAngle)

    def __rotation(self, stepSize: int):
        """
        指定图片中心并旋转
        :return:
        """
        self.pixmapItem.setTransformOriginPoint(self.pixmapItem.boundingRect().center())  # 指定图片旋转中心点
        self.pixmapItem.setRotation(stepSize)
        self.setAdaptation()

    def __isEnableDrag(self):
        """
        根据图片的尺寸决定是否启动拖拽功能
        :return:
        """
        v = self.verticalScrollBar().maximum() > 0
        h = self.horizontalScrollBar().maximum() > 0
        return v or h

    def __setDragEnabled(self, isEnabled: bool):
        """
        设置拖拽是否启动
        :param isEnabled: bool
        :return:
        """
        if isEnabled:
            self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
        else:
            self.setDragMode(QGraphicsView.DragMode.NoDrag)

    def __getScaleRatio(self):
        """
        获取显示的图像和原始图像的缩放比例
        :return:
        """
        if self.pixmap.isNull():
            return 1

        pw = self.pixmap.width()
        ph = self.pixmap.height()
        rw = min(1, self.width() / pw)
        rh = min(1, self.height() / ph)
        return min(rw, rh)

    def enlargePicture(self, anchor=QGraphicsView.AnchorUnderMouse):
        """
        放大图片
        :return:
        """
        if self.zoomInTimes == self.maxZoomInTimes:
            return
        self.setTransformationAnchor(anchor)
        self.zoomInTimes += 1
        self.scale(1.1, 1.1)
        self.__setDragEnabled(self.__isEnableDrag())

        # 还原 anchor
        self.setTransformationAnchor(self.AnchorUnderMouse)

    def shrinkPicture(self, anchor=QGraphicsView.AnchorUnderMouse):
        """
        缩小图片
        :return:
        """
        if self.zoomInTimes == 0 and not self.__isEnableDrag():
            return

        self.setTransformationAnchor(anchor)

        self.zoomInTimes -= 1

        # 原始图像的大小
        pw = self.pixmap.width()
        ph = self.pixmap.height()

        # 实际显示的图像宽度
        w = self.displayedImageSize.width() * 1.1 ** self.zoomInTimes
        h = self.displayedImageSize.height() * 1.1 ** self.zoomInTimes

        if pw > self.width() or ph > self.height():
            # 在窗口尺寸小于原始图像时禁止继续缩小图像比窗口还小
            if w <= self.width() and h <= self.height():
                self.fitInView(self.pixmapItem)
            else:
                self.scale(1 / 1.1, 1 / 1.1)
        else:
            # 在窗口尺寸大于图像时不允许缩小的比原始图像小
            if w <= pw:
                self.resetTransform()
            else:
                self.scale(1 / 1.1, 1 / 1.1)

        self.__setDragEnabled(self.__isEnableDrag())

        # 还原 anchor
        self.setTransformationAnchor(self.AnchorUnderMouse)

    def getZoomInTimes(self, width: int, step: int = 100):
        for i in range(0, self.maxZoomInTimes):
            if width - self.displayedImageSize.width() * 1.1 ** i <= step:
                return i
        return self.maxZoomInTimes

    def fitInView(self, item: QGraphicsItem, mode=Qt.AspectRatioMode.KeepAspectRatio):
        """
        缩放场景使其适应窗口大小
        :param item:
        :param mode:
        :return:
        """
        super().fitInView(item, mode)
        self.displayedImageSize = self.__getScaleRatio() * self.pixmap.size()
        self.zoomInTimes = 0

    def resizeEvent(self, event: QResizeEvent):
        if self.zoomInTimes > 0:
            return
        # 调整图片大小
        ratio = self.__getScaleRatio()
        self.displayedImageSize = self.pixmap.size() * ratio
        if ratio < 1:
            self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
        else:
            self.resetTransform()

    def resetTransform(self):
        """
        重置变换
        :return:
        """
        self.zoomInTimes = 0
        self.__setDragEnabled(False)
        super().resetTransform()

    def wheelEvent(self, e: QWheelEvent):
        """
        滚动鼠标滚轮缩放图片
        :param e:
        :return:
        """
        if e.angleDelta().y() > 0:
            self.enlargePicture()
        else:
            self.shrinkPicture()
